package org.dromara.generator.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
import org.dromara.generator.service.IGenTableService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：代码生成 操作处理
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/tool/gen")
public class GenController extends BaseController {

    /**
     * 业务 服务层
     */
    private final IGenTableService genTableService;

    /**
     * 查询代码生成列表
     * @param genTable    代码生成表信息
     * @param pageQuery   分页查询条件
     * @return TableDataInfo<GenTable> 返回分页的代码生成表列表信息
     */
    @SaCheckPermission("tool:gen:list")
    @GetMapping("/list")
    public TableDataInfo<GenTable> genList(GenTable genTable, PageQuery pageQuery) {
        return genTableService.selectPageGenTableList(genTable, pageQuery);
    }

    /**
     * 修改代码生成业务
     * 根据表ID查询代码生成配置信息
     *
     * @param tableId 表ID
     * @return 返回代码生成配置信息的封装Map
     */
    @SaCheckPermission("tool:gen:query")
    @GetMapping(value = "/{tableId}")
    public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
        GenTable table = genTableService.selectGenTableById(tableId);
        List<GenTable> tables = genTableService.selectGenTableAll();
        List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
        Map<String, Object> map = new HashMap<>(3);
        map.put("info", table);
        map.put("rows", list);
        map.put("tables", tables);
        return R.ok(map);
    }

    /**
     * 查询数据库列表
     * 查询代码生成的数据库表列表
     * 此方法需要tool:gen:list权限才能访问
     * 通过GET请求 /tool/gen/db/list 获取数据库表分页列表
     *
     * @param genTable   数据库表信息查询条件
     * @param pageQuery  分页查询条件
     * @return 返回数据库表的分页数据信息
     */
    @SaCheckPermission("tool:gen:list")
    @GetMapping("/db/list")
    public TableDataInfo<GenTable> dataList(GenTable genTable, PageQuery pageQuery) {
        return genTableService.selectPageDbTableList(genTable, pageQuery);
    }

    /**
     * 查询数据表字段列表
     *
     * 本方法需要工具生成的权限才能访问
     * 它通过表ID查询相应的列信息，支持分页展示
     *
     * @param tableId 表ID，用于标识特定的表
     * @return 返回一个包含表列信息的数据表对象
     */
    @SaCheckPermission("tool:gen:list")
    @GetMapping(value = "/column/{tableId}")
    public TableDataInfo<GenTableColumn> columnList(@PathVariable("tableId") Long tableId) {
        TableDataInfo<GenTableColumn> dataInfo = new TableDataInfo<>();
        List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
        dataInfo.setRows(list);
        dataInfo.setTotal(list.size());
        return dataInfo;
    }

    /**
     * 导入表结构（保存）
     *
     * @param tables   多个表名，以逗号分隔
     * @param dataName 数据库名称
     * @return 成功或失败的消息
     */
    @SaCheckPermission("tool:gen:import")
    @Log(title = "代码生成", businessType = BusinessType.IMPORT)
    @PostMapping("/importTable")
    public R<Void> importTableSave(String tables, String dataName) {
        String[] tableNames = Convert.toStrArray(tables);
        // 查询表信息
        List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames, dataName);
        genTableService.importGenTable(tableList, dataName);
        return R.ok();
    }

    /**
     * 修改保存代码生成业务
     * 通过校验用户是否具有"tool:gen:edit"权限来控制对代码生成功能的编辑访问
     * 日志记录此操作为"代码生成"的更新类型
     *
     * @param genTable 代码生成的表信息，包含更新所需的全部字段
     * @return R<Void> 表示操作结果，包含是否成功及相应消息
     */
    @SaCheckPermission("tool:gen:edit")
    @Log(title = "代码生成", businessType = BusinessType.UPDATE)
    @PutMapping
    public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
        genTableService.validateEdit(genTable);
        genTableService.updateGenTable(genTable);
        return R.ok();
    }

    /**
     * 删除代码生成
     *
     * @param tableIds 需要删除的表ID数组
     * @return 删除操作的结果
     */
    @SaCheckPermission("tool:gen:remove")
    @Log(title = "代码生成", businessType = BusinessType.DELETE)
    @DeleteMapping("/{tableIds}")
    public R<Void> remove(@PathVariable Long[] tableIds) {
        genTableService.deleteGenTableByIds(tableIds);
        return R.ok();
    }

    /**
     * 预览代码
     * 此方法用于根据指定的表ID预览生成的代码，确保只有拥有相应权限的用户可以访问
     * 它通过RESTful方式处理HTTP GET请求，旨在为前端提供一个预览功能，以便在实际生成代码之前查看其内容
     *
     * @param tableId 表ID，用于标识需要生成代码的特定表
     * @return 返回包含生成代码的Map对象，键是文件名，值是对应的代码内容
     * @throws IOException 如果在代码生成过程中发生问题，则抛出IO异常
     */
    @SaCheckPermission("tool:gen:preview")
    @GetMapping("/preview/{tableId}")
    public R<Map<String, String>> preview(@PathVariable("tableId") Long tableId) throws IOException {
        Map<String, String> dataMap = genTableService.previewCode(tableId);
        return R.ok(dataMap);
    }

    /**
     * 生成代码（下载方式）
     *
     * 该方法用于响应用户在Web界面下载代码生成文件的请求它通过表ID获取相应的代码生成数据，并将其作为文件下载响应返回
     *
     * @param response HTTP响应对象，用于封装下载的文件返回给客户端
     * @param tableId 通过路径变量传入的代码生成表ID，用于标识需要下载的代码文件
     * @throws IOException 如果无法读取文件或写入响应流时发生错误，将抛出此异常
     */
    @SaCheckPermission("tool:gen:code")
    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
    @GetMapping("/download/{tableId}")
    public void download(HttpServletResponse response, @PathVariable("tableId") Long tableId) throws IOException {
        byte[] data = genTableService.downloadCode(tableId);
        genCode(response, data);
    }

    /**
     * 生成代码（自定义路径）
     * 此方法的目的是通过指定的表ID，触发代码生成服务，以实现自动化代码生成的功能
     * 它不需要请求体（返回类型为R<Void>），仅需接收一个路径变量tableId作为输入
     * 在方法内部，它调用genTableService的generatorCode方法进行代码生成
     * 最后，返回一个表示操作成功的结果（R.ok()）
     *
     * @param tableId 表ID
     * @return R<Void> 表示操作结果
     */
    @SaCheckPermission("tool:gen:code")
    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
    @GetMapping("/genCode/{tableId}")
    public R<Void> genCode(@PathVariable("tableId") Long tableId) {
        genTableService.generatorCode(tableId);
        return R.ok();
    }

    /**
     * 同步数据库
     * 根据表ID同步数据库信息
     *
     * 此方法用于更新指定表的数据库信息，确保数据库表结构与代码生成器中的表定义一致
     * 需要具备"tool:gen:edit"权限才能执行此操作
     *
     * @param tableId 表示要同步数据库信息的表ID
     * @return 返回一个响应对象，表示操作是否成功
     */
    @SaCheckPermission("tool:gen:edit")
    @Log(title = "代码生成", businessType = BusinessType.UPDATE)
    @GetMapping("/synchDb/{tableId}")
    public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
        genTableService.synchDb(tableId);
        return R.ok();
    }

    /**
     * 批量生成代码
     *
     * 本方法通过GET请求提供了一个接口，用于根据指定的表ID批量生成代码
     * 它首先检查用户是否有权限进行代码生成，然后调用服务下载相应的代码，并将其响应给客户端
     *
     * @param response 用于向客户端输出生成的代码文件的HttpServletResponse对象
     * @param tableIdStr 以逗号分隔的表ID字符串，这些表的代码将被生成
     * @throws IOException 如果无法将生成的代码正确写入HTTP响应中，则可能抛出此异常
     */
    @SaCheckPermission("tool:gen:code")
    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
    @GetMapping("/batchGenCode")
    public void batchGenCode(HttpServletResponse response, String tableIdStr) throws IOException {
        // 将传入的表ID字符串转换为字符串数组
        String[] tableIds = Convert.toStrArray(tableIdStr);
        // 调用genTableService服务来下载指定表的代码
        byte[] data = genTableService.downloadCode(tableIds);
        // 调用内部方法genCode，将生成的代码作为HTTP响应返回给客户端
        genCode(response, data);
    }

    /**
     * 生成zip文件
     * 将代码生成为文件并提供下载
     * 该方法主要用于响应HTTP请求，将生成的代码数据作为附件下载
     * 它通过修改HTTP响应头，使得客户端可以进行跨域请求，并明确响应内容的处理方式
     *
     * @param response HTTP响应对象，用于向客户端发送数据和状态信息
     * @param data 要下载的代码数据，以字节数组形式表示
     * @throws IOException 如果在写入响应输出流时发生I/O错误
     */
    private void genCode(HttpServletResponse response, byte[] data) throws IOException {
        // 重置响应，以避免有现有的响应头或状态冲突
        response.reset();
        // 允许跨域访问，让所有域都可以请求该资源
        response.addHeader("Access-Control-Allow-Origin", "*");
        // 允许跨域访问时暴露Content-Disposition头，以便客户端可以识别附件下载
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
        // 设置响应的Content-Disposition头，指明这是一个附件，客户端应提示用户下载，文件名为ruoyi.zip
        response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\"");
        // 设置响应内容的长度，以便客户端可以知道预期接收到的数据量
        response.addHeader("Content-Length", "" + data.length);
        // 设置响应的内容类型，表示这是一个二进制流，同时指定字符集为UTF-8
        response.setContentType("application/octet-stream; charset=UTF-8");
        // 将字节数组写入HTTP响应的输出流，实现文件下载
        IoUtil.write(response.getOutputStream(), false, data);
    }

    /**
     * 查询数据源名称列表
     * 该方法用于获取系统中当前配置的数据源的名称列表，以便在其他地方使用这些名称进行数据操作
     *
     * @return 返回一个封装了数据源名称列表的对象
     */
    @SaCheckPermission("tool:gen:list")
    @GetMapping(value = "/getDataNames")
    public R<Object> getCurrentDataSourceNameList(){
        return R.ok(DataBaseHelper.getDataSourceNameList());
    }
}
